#include <stdio.h>   
#include <math.h>
#include <string.h>
#include <cutil.h>
#include <cuda.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>

#include "svmTrain.h"
#include "../common/framework.h"
#include "../common/deviceSelect.h"
#include "../common/svmIO.h"
#include "Cache.h"
#include "Controller.h"
#include "svmKernels.h"

const int extLength = 7;
const char extModel[] = ".model";

void printHelp() {
	printf("Usage: svmTrain [options] trainingData.svm\n"
		"Options:\n"
		"	-o outputFilename	 Location of output file\n"
		"Kernel types:\n"
		"	-k(default 0)\n"
		"	0 Gaussian or RBF kernel : Phi(x, y; gamma) = exp{-gamma*||x-y||^2}\n"
		"	1 Linear kernel: Phi(x, y) = x . y\n"
		"	2 Polynomial kernel: Phi(x, y; a, r, d) = (ax . y + r)^d\n"
		"	3 Sigmoid kernel: Phi(x, y; a, r) = tanh(ax . y + r)\n"
		"Parameters:\n"
		"	-c	SVM training cost C (default = 10)\n"
		"	-g	Gamma for Gaussian kernel (default = 1/l)\n"
		"	-a	Parameter a for Polynomial and Sigmoid kernels (default = 1/l)\n"
		"	-r	Parameter r for Polynomial and Sigmoid kernels (default = 1)\n"
		"	-d	Parameter d for Polynomial kernel (default = 3)\n"
		"Convergence parameters:\n"
		"	-t -- tolerance:Termination criterion tolerance (default = 0.001)\n"
		"	-e -- epsilon:Support vector threshold (default = 1e-5)\n"
		"Internal options:\n"
		"	-h	Working selection heuristic:\n"
		"	0: First order\n"
		"	1: Second order\n"
		"	2: Random (either first or second order)\n"
		"	3: Adaptive (default)\n");
}

int main( const int argc, const char** argv)  {
	KernelParams kp;
	kp.kType = GAUSSIAN;
	float parameterA = -0.125f;
	float parameterB = 1.0f;
	float parameterC = 3.0f;

	bool parameterASet = false;
	bool parameterBSet = false;
	bool parameterCSet = false;

	SelectionHeuristic heuristicMethod = FIRSTORDER;
	float cost = 10.0f;
	float tolerance = 1e-3f;
	float epsilon = 1e-5f;

	char* outputFilename = NULL;
	int idx;

	printf("%s Starting...\n",argv[0]);
	int devID = chooseLargestGPU(true);

	if(argc < 2) {
		printHelp();
		return 0;
	}
	for(idx=1;idx<argc;idx++) {
		if(argv[idx][0] != '-') break;
		if(++idx >= argc) break;
		switch(argv[idx-1][1]) {
			case 0: break;
			case 'v':
				//printf("GPUSVM version %1.1f\n", VERSION);
				puts("gpusvm version in ix7");
				exit(1); break;
			case 'f':
				printHelp();
				exit(1); break;
			case 'c':
				cost = (float)atof(argv[idx]);
				break;
			case 'k':
				kp.kType = (KernelType)atoi(argv[idx]);
				break;
			case 'h':
				switch(atoi(argv[idx])) {
					case 0:
						heuristicMethod = FIRSTORDER; break;
					case 1:
						heuristicMethod = SECONDORDER; break;
					//case 2:
					//	heuristicMethod = FIRSTORDER_MODIFIED; break;
					//case 3:
					//	heuristicMethod = SECONDORDER_MODIFIED; break;
					default: break;
				}
				break;
			//case 'm':
			//	switch(atoi(argv[idx]) {
			//		case 0:
			//			multiflag = false; break;
			//		case 1:
			//			multiflag = true; break;
			//		default: break;
			//	}
			//	break;
			case 't':
				tolerance = (float)atof(argv[idx]);
				break;
			case 'e':
				epsilon = (float)atof(argv[idx]);
				break;
			case 'o':
				outputFilename = (char*)malloc(strlen(argv[idx]));
				strcpy(outputFilename,argv[idx]);
				break;
			case 'a':
				parameterA = (float)atof(argv[idx]);
				parameterASet = true;
				break;
			case 'r':
				parameterB = (float)atof(argv[idx]);
				parameterBSet = true;
				break;
			case 'd':
				parameterC = (float)atof(argv[idx]);
				parameterCSet = true;
				break;
			case 'g':
				parameterA = (float)atof(argv[idx]);
				parameterA = -parameterA;
				parameterASet = true;
				break;
			default:
				printHelp();
				exit(1);
				break;
		}
	}
	
	const char* trainingFilename = argv[idx];

	//If [outputfilename is null]
	//then [add the extention defined above to the inputfilename] and
	//     [set it as outputfile name]
	if (outputFilename == NULL) {
		int inputNameLength = strlen(trainingFilename);
		outputFilename = (char*)malloc(sizeof(char)*(inputNameLength + extLength));
		strcpy(outputFilename,trainingFilename);
		char* period = strrchr(outputFilename, '.');
		if (period == NULL)  //try to overwrite the file extention name of inputfile
			period = outputFilename + inputNameLength;
		strncpy(period, extModel, extLength);
	}

	int nPoints;
	int nDimension;
	float* data;
	float* transposedData;
	float* labels;
	readSvm(trainingFilename, &data, &labels, &nPoints, &nDimension, &transposedData);
	printf("Input data found: %d points, %d dimension\n", nPoints, nDimension);

	float* alpha;
	
	if (kp.kType == LINEAR) {
		printf("Linear kernel\n");
		//kp.kernel_type = "linear";
	} else if (kp.kType == POLYNOMIAL) {
		if (!parameterCSet) parameterC = 3.0f;
		if (!parameterASet) parameterA = 1.0f/nPoints;
		if (!parameterBSet) parameterB = 0.0f;
		//printf("Polynomial kernel: a = %f, r = %f, d = %f\n", parameterA, parameterB, parameterC);
		if (parameterA<=0 || parameterB < 0 || parameterC < 1.0f) {
			printf("Invalid parameters\n");
			exit(1);
		}
		kp.gamma = parameterA;
		kp.coef0 = parameterB;
		kp.degree = (int)parameterC;
	} else if (kp.kType == GAUSSIAN) {
		if (!(parameterASet)) {
			parameterA = 1.0f/nPoints;
		} else {
			parameterA = -parameterA;
		}
		//printf("Gaussian kernel: gamma = %f\n", parameterA);
		if (parameterA < 0) {
			printf("Invalid parameters\n");
			exit(1);
		}
		kp.gamma = parameterA;
	} else if (kp.kType == SIGMOID) {
		if (!(parameterASet)) {
			parameterA = 1.0f/nPoints;
		}
		if (!(parameterBSet)) {
			parameterB = 0.0f;
		}
		//printf("Sigmoid kernel: a = %f, r = %f\n", parameterA, parameterB);
		if ((parameterA <= 0) || (parameterB < 0)) {
			printf("Invalid Parameters\n");
			exit(1);
		}
		kp.gamma = parameterA;
		kp.coef0 = parameterB;
	}

	//use clock_t as a timer
	clock_t start,stop;
	start = clock();

	performTraining(data, nPoints, nDimension, labels, &alpha, &kp, cost, heuristicMethod, epsilon, tolerance, transposedData);

	stop = clock();
	float trainingTime = ((float)(stop-start)/CLOCKS_PER_SEC);

	printf("Training time : %f seconds\n", trainingTime);
	printModel(outputFilename, kp, alpha, labels, data, nPoints, nDimension, epsilon);
	
	return 0;
}